package com.gmaf.mas.launch;

import java.io.File;
import java.net.URL;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.w3c.dom.*;

import com.gmaf.mas.launch.property.DefaultConfiguration;
import com.gmaf.mas.launch.property.DefaultPropertyParser;
import com.gmaf.mas.launch.property.Property;
import com.gmaf.mas.launch.resloader.ClassLoaderResourceLoader;
import com.gmaf.mas.launch.resloader.CompositeResourceLoader;
import com.gmaf.mas.launch.resloader.FileResourceLoader;
import com.gmaf.mas.launch.resloader.ResourceLoader;
import com.gmaf.mas.launch.resloader.ResourceLoaderBuilder;

/**
 * Referred by org.apache.commons.configuration.ConfigurationUtils
 */
public class Locator
{
	private static final Log log = LogFactory.getLog(Locator.class);

	private static ResourceLoader defaultLoader = null;
	private static ResourceLoader appLoader = null;

	static
	{
		defaultLoader = getDefaultLoader();
		if(log.isDebugEnabled())
		{
			log.debug("defaultLoader=" + defaultLoader);
		}

		URL configURL = locate("config/ResourceLoader.xml");
		if(configURL != null)
		{
			appLoader = buildResourceLoader(configURL);
			if(log.isDebugEnabled())
			{
				log.debug("appLoader=" + appLoader);
			}
		}
	}

	private Locator()
	{
	}

	public static URL locate(String name)
	{
		URL url = getLoader().getResource(name);
		if(log.isDebugEnabled())
		{
			StringBuilder sb = new StringBuilder("locate resource [");
			sb.append(name).append("] at URL: ");
			sb.append((url != null) ? url.toString() : "null");
			log.debug(sb.toString());
		}
		return url;
	}

	private static ResourceLoader getLoader()
	{
		if(appLoader != null)
		{
			return appLoader;
		}
		return defaultLoader;
	}

	private static ResourceLoader getDefaultLoader()
	{
		CompositeResourceLoader crl = new CompositeResourceLoader();

		// Application Loader
		ResourceLoader rl = getAppHomeLoader();
		if(rl != null)
		{
			crl.getResourceLoaders().add(rl);
		}

		// Current directory Loader
		crl.getResourceLoaders().add(new FileResourceLoader("."));

		// User home directory Loader
		crl.getResourceLoaders().add(new FileResourceLoader(System.getProperty("user.home")));

		addClassLoaders(crl);

		return crl;
	}

	private static ResourceLoader getAppHomeLoader()
	{
		String appHome = null;

		do
		{
			String appHomeKey = null;

			String appName = System.getProperty("app.name");
			if(!StringUtils.isEmpty(appName))
			{
				appHomeKey = System.getProperty(appName + ".home");
			}

			if(StringUtils.isEmpty(appHomeKey))
			{
				appHomeKey = "app.home";
			}

			appHome = System.getProperty(appHomeKey);
			
			if(appHome != null)
			{
				System.setProperty(appHomeKey + "WithSlash", appHome + File.separator);
			}
		}
		while(false);

		if(!StringUtils.isEmpty(appHome))
		{
			return new FileResourceLoader(appHome);
		}
		return null;
	}
	
	private static ResourceLoader buildResourceLoader(URL configURL)
	{
		DefaultConfiguration config = new DefaultConfiguration(configURL);
		DefaultPropertyParser parser = new DefaultPropertyParser();
		
		CompositeResourceLoader crl = new CompositeResourceLoader();
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		try
		{
			DocumentBuilder builder = factory.newDocumentBuilder();

			// Parse
			Document document = builder.parse(configURL.openStream());

			// Get root
			Element root = document.getDocumentElement();
			NodeList nodeList = root.getChildNodes();

			Node element;
			for(int i = 0, length = nodeList.getLength(); i < length; i++)
			{
				element = nodeList.item(i);
				if(element.getNodeType() != Node.ELEMENT_NODE)
				{
					continue;
				}
				
				NamedNodeMap attribMap = element.getAttributes();
				
				if(element.getNodeName().equals("property"))
				{
					Property property = new Property();
					property.setName(getAttibuteValue(attribMap, "name"));
					property.setValue(getAttibuteValue(attribMap, "value"));
					property.setLocation(getAttibuteValue(attribMap, "location"));
					
					property.execute(parser, config);
					continue;
				}
				
				if(element.getNodeName().equals("loader"))
				{
					ResourceLoaderBuilder rlb = new ResourceLoaderBuilder(crl.getResourceLoaders());
					rlb.setType(getAttibuteValue(attribMap, "type"));
					rlb.setValue(getAttibuteValue(attribMap, "value"));
					
					rlb.execute(parser, config);
					continue;
				}
			}
		}
		catch(Exception e)
		{
			log.error("buildResourceLoader failed. URL=" + configURL.toString(), e);
		}

		if(!crl.getResourceLoaders().isEmpty())
		{
			addClassLoaders(crl);
		}
		
		if(log.isDebugEnabled())
		{
			log.debug("BuildIn: " + config.getBuildInProperties());
			log.debug("Local: " + config.getLocalProperties());
		}
		
		return crl.getResourceLoaders().isEmpty() ? null : crl;
	}

	private static String getAttibuteValue(NamedNodeMap attribMap, String name)
	{
		Node attrib = attribMap.getNamedItem(name);
		if(attrib == null)
		{
			return null;
		}
		return attrib.getNodeValue();
	}
	
	private static void addClassLoaders(CompositeResourceLoader crl)
	{
		// Context ClassLoader
		ClassLoader cxtLoader = Thread.currentThread().getContextClassLoader();
		if(cxtLoader != null)
		{
			crl.getResourceLoaders().add(new ClassLoaderResourceLoader(cxtLoader));
		}

		// System ClassLoader
		crl.getResourceLoaders().add(ClassLoaderResourceLoader.getSystemLoader());
	}
}
